package com.hss.thread.wait;

import java.util.LinkedList;

/**
 *    https://blog.csdn.net/vk5176891/article/details/53945677
 * wait，notify，notifyAll 是定义在Object类的实例方法，用于控制线程状态。
 *
 * 三个方法都必须在synchronized 同步关键字所限定的作用域中调用，否则会报错java.lang.IllegalMonitorStateException ，意思是因为没有同步，所以线程对对象锁的状态是不确定的，不能调用这些方法。
 *
 * wait 表示持有对象锁的线程A准备释放对象锁权限，释放cpu资源并进入等待。
 * notify 表示持有对象锁的线程A准备释放对象锁权限，通知jvm唤醒某个竞争该对象锁的线程X。线程A synchronized 代码作用域结束后，线程X直接获得对象锁权限，其他竞争线程继续等待(即使线程X同步完毕，释放对象锁，其他竞争线程仍然等待，直至有新的notify ,notifyAll被调用)。
 * notifyAll 表示持有对象锁的线程A准备释放对象锁权限，通知jvm唤醒所有竞争该对象锁的线程，线程A synchronized 代码作用域结束后，jvm通过算法将对象锁权限指派给某个线程X，所有被唤醒的线程不再等待。线程X synchronized 代码作用域结束后，之前所有被唤醒的线程都有可能获得该对象锁权限，这个由JVM算法决定。
 * wait有三个重载方法，同时必须捕获非运行时异常InterruptedException。
 *
 * wait() 进入等待，需要notify ,notifyAll才能唤醒
 * wait(long timeout) 进入等待，经过timeout 超时后，若未被唤醒，则自动唤醒
 * wait(timeout, nanos) 进入等待，经过timeout 超时后，若未被唤醒，则自动唤醒。相对wait(long timeout) 更加精确时间。
 * ---------------------
 *
 *
 * */
public class WaitTest {

    public  static  interface AbstractStorage {
        void consume(int num);
        void produce(int num);
    }


    /**
     *  生产者和消费者的问题
     *  wait、notify/notifyAll() 实现
     */
    public static class Storage1 implements AbstractStorage {
        //仓库最大容量
        private final int MAX_SIZE = 100;
        //仓库存储的载体
        private LinkedList list = new LinkedList();

        //生产产品
        public void produce(int num){
            //同步
            synchronized (list){
                //仓库剩余的容量不足以存放即将要生产的数量，暂停生产
                while(list.size()+num > MAX_SIZE){
                    System.out.println("【要生产的产品数量】:" + num + "\t【库存量】:"
                            + list.size() + "\t暂时不能执行生产任务!");

                    try {
                        //条件不满足，生产阻塞
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                for(int i=0;i<num;i++){
                    list.add(new Object());
                }

                System.out.println("【已经生产产品数】:" + num + "\t【现仓储量为】:" + list.size());

                list.notifyAll();
            }
        }

        //消费产品
        public void consume(int num){
            synchronized (list){

                //不满足消费条件
                while(num > list.size()){
                    System.out.println("【要消费的产品数量】:" + num + "\t【库存量】:"
                            + list.size() + "\t暂时不能执行生产任务!");

                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //消费条件满足，开始消费
                for(int i=0;i<num;i++){
                    list.remove();
                }

                System.out.println("【已经消费产品数】:" + num + "\t【现仓储量为】:" + list.size());

                list.notifyAll();
            }
        }
    }

    public static  class Producer extends Thread{
        //每次生产的数量
        private int num ;

        //所属的仓库
        public AbstractStorage abstractStorage;

        public Producer(AbstractStorage abstractStorage){
            this.abstractStorage = abstractStorage;
        }

        public void setNum(int num){
            this.num = num;
        }

        // 线程run函数
        @Override
        public void run()
        {
            produce(num);
        }

        // 调用仓库Storage的生产函数
        public void produce(int num)
        {
            abstractStorage.produce(num);
        }
    }

    public static class Consumer extends Thread{
        // 每次消费的产品数量
        private int num;

        // 所在放置的仓库
        private AbstractStorage abstractStorage1;

        // 构造函数，设置仓库
        public Consumer(AbstractStorage abstractStorage1)
        {
            this.abstractStorage1 = abstractStorage1;
        }

        // 线程run函数
        public void run()
        {
            consume(num);
        }

        // 调用仓库Storage的生产函数
        public void consume(int num)
        {
            abstractStorage1.consume(num);
        }

        public void setNum(int num){
            this.num = num;
        }
    }

    public static void main(String[] args) {
                // 仓库对象
                AbstractStorage abstractStorage = new Storage1();

                // 生产者对象
                Producer p1 = new Producer(abstractStorage);
                Producer p2 = new Producer(abstractStorage);
                Producer p3 = new Producer(abstractStorage);
                Producer p4 = new Producer(abstractStorage);
                Producer p5 = new Producer(abstractStorage);
                Producer p6 = new Producer(abstractStorage);
                Producer p7 = new Producer(abstractStorage);

                // 消费者对象
                Consumer c1 = new Consumer(abstractStorage);
                Consumer c2 = new Consumer(abstractStorage);
                Consumer c3 = new Consumer(abstractStorage);

                // 设置生产者产品生产数量
                p1.setNum(10);
                p2.setNum(10);
                p3.setNum(10);
                p4.setNum(10);
                p5.setNum(10);
                p6.setNum(10);
                p7.setNum(80);

                // 设置消费者产品消费数量
                c1.setNum(50);
                c2.setNum(20);
                c3.setNum(30);

                // 线程开始执行
                c1.start();
                c2.start();
                c3.start();

                p1.start();
                p2.start();
                p3.start();
                p4.start();
                p5.start();
                p6.start();
                p7.start();
    }
}
